'''
Created on Jul 27, 2011

@author: vijayp
'''
import csv
import logging
import datetime

class UnsupportedAccountTypeError(Exception):
  pass
class qif(object):
  ''' Generates quicken-compliant QIF files. You can trick quicken into
  opening qif files for certain accounts by matching the account name
  exactly. see:
  http://en.wikipedia.org/wiki/Quicken_Interchange_Format
  and
  https://qlc.intuit.com/post/show_full/dQMNaWtX4r3OK1acfArQA8?fullpage=qna&page=1&ppid=127878160
  
  !Account
NMy savings
TBank

   '''
  HEADER = '''\
!Account
N%(name)s
T%(type)s
^'''
  SECTION_HEADER = '''!Type:%(type)s'''

  def __init__(self, account_name, account_type):
    object.__init__(self)
    if account_type not in ['Bank', 'CCard', 'Invst'] :
      raise UnsupportedAccountTypeError, account_type

    self._account_name = account_name
    self._account_type = account_type
    self._outs = []
    self._outs.append(qif.HEADER % dict(name=self._account_name, type=self._account_type))
    self.set_header()
    self._entries = []

  def add_entry(self, qif_entry):
    assert isinstance (qif_entry, base_qif_entry)
    self._entries.append(qif_entry)
    self._outs.append(qif_entry.str())

  def set_header(self, t=None):
    if t is None:
      t = self._account_type
    self._outs.append(self.SECTION_HEADER % {'type' : t})

  def str(self):
    return '\n'.join(self._outs)



class base_qif_entry(object):
  def __init__(self, **kwargs):
    self._data = {}
    self._data.update(kwargs)

  def str(self):
    outs = ['^']
    outs += ['%s%s' % kv for kv in self._data.iteritems()]
    return '\n'.join(outs)

class ticker_qif_entry(base_qif_entry):
  def __init__(self, name, symbol, **kwargs):
    base_qif_entry.__init__(self, N=name, S=symbol, T='Stock', **kwargs)

class simple_qif_entry(base_qif_entry):
  '''
^
D2 / 7'11
NShrsIn
YHAWAIIAN ELEC INDUSTRIES
Q133
Cc
MHAWAIIAN ELEC INDUSTRIES
'''

  def __init__(self, date_obj=None, cleared='c', **kwargs):
    assert date_obj
    self.date = date_obj
    base_qif_entry.__init__(self, D=date_obj.strftime('%d %B %Y'),
                            C=cleared, **kwargs)


class shares_qif_entry(simple_qif_entry):
  def __init__(self, is_xfer_in, security_name, quantity, memo=None, **kwargs):
    simple_qif_entry.__init__(self, **kwargs)
    self._data['Y'] = security_name
    self._data['M'] = memo if memo is not None else security_name
    self._data['Q'] = quantity
    self._data['N'] = 'ShrsIn' if is_xfer_in else 'ShrsOut'


'''
  !Type:Security
  N3 Com Corp
  SCOMS
  TStock
  ^
'''


if __name__ == '__main__':
    pass

make_date = lambda date_str, fmt: datetime.datetime.strptime(date_str, fmt)

class BaseCSVImporter(object):
  PARAM_MAKERS_MAPPING = {}

  @classmethod
  def LINE_TO_OBJ_MAPPING(cls, row):
    return shares_qif_entry

  #default to US format
  DATE_FORMAT = '%m/%d/%Y'

  @classmethod
  def post_process(cls, qif, row):
    pass

  @classmethod
  def import_to_qif(cls, filename, qif, ticker_to_name_map=None):
    if ticker_to_name_map is None:
      ticker_to_name_map = {}
    reader = csv.DictReader(open(filename).readlines(), dialect='excel')
    for i in range(len(reader.fieldnames)):
      reader.fieldnames[i] = reader.fieldnames[i].strip()
    logging.info('got file with headers %s' % str(reader.fieldnames))
    for row in reader:
      try:
        qclass = cls.LINE_TO_OBJ_MAPPING(row)
        kwargs = {}
        assert cls.PARAM_MAKERS_MAPPING
        for k, v in cls.PARAM_MAKERS_MAPPING.items():
          if v(row) is not None:
            kwargs[k] = v(row)
        cls.post_process(kwargs, row, ticker_to_name_map)
        qif.add_entry(qclass(**kwargs))
      except Exception, e:
        logging.error('ignoring error %s for data <%s>',
                      e,
                      str(row))

class AperioCSVImporter(BaseCSVImporter):
  PARAM_MAKERS_MAPPING = {'date_obj' : (lambda r: make_date(r['PURCHASE DATE'], AperioCSVImporter.DATE_FORMAT)),
                            'security_name' : (lambda r:r.get('DESCRIPTION')),
                            'quantity' : (lambda r:r.get('SHARES')),
                            'is_xfer_in' : (lambda r:True if float(r.get('COST', '0').replace(',', '').replace(' ', '')) > 0 else False),
                            'T' : (lambda r: r.get('COST')),
                            'I' : (lambda r: r.get('COST PER SHARE')),
                            }
  @classmethod
  def post_process(cls, kwargs, row, ttn=None):
    if not ttn:
      return
    ticker = row.get('TICKER', '').strip()
    if ticker in ttn:
      logging.info('replacing name <%s> with <%s> due to match of ticker [%s]',
                    kwargs['security_name'], ttn[ticker], ticker)

      kwargs['security_name'] = ttn[ticker]
    else:
      logging.info('not replacing name <%s> due to missing ticker [%s]',
                    kwargs['security_name'], ticker)


def GetTickerToNameMap(fn):
  #TODO make this a lot nicer
  rval = {}
  name = None
  stock = None
  for line in open(fn):
    line = line.strip()
    if line[0] not in ['N', 'S']:
      continue

    if line.startswith('N'):
      name = line[1:].strip() # for newline
    if line.startswith('S') and name is not None:
      stock = line[1:].strip()
      rval[stock] = name

  return rval

class AperioCSVTickerImporter(BaseCSVImporter):
  @classmethod
  def LINE_TO_OBJ_MAPPING(cls, row):
    return ticker_qif_entry
  PARAM_MAKERS_MAPPING = {'name' : (lambda r:r.get('DESCRIPTION')),
                          'symbol' : (lambda r:r.get('TICKER')),
                            }

